#!/usr/bin/env python
# coding: utf-8
"""
异地同步备份插件 - 定时调度模块
功能：定期检查并执行同步任务

使用方式：
    python schedule.py check_sync              # 检查并执行到期的同步任务
    python schedule.py run                     # 立即运行所有同步任务
    python schedule.py run <config_name>       # 立即运行指定的同步任务

Author: yang
Version: 2.0.0
"""

import os
import sys
import psutil
import fcntl
from croniter import croniter
from datetime import datetime

import helpr
import psync

# 设置运行目录
os.chdir("/www/server/panel")
# 添加包引用位置并引用公共包
sys.path.append("class/")
import public
import panelTask

# 插件路径
_PLUGIN_PATH = "/www/server/panel/plugin/remoteback/"
# 锁文件路径
_LOCK_FILE = _PLUGIN_PATH + "config/.sync_running.lock"


def is_sync_running(config_name=None):
    """
    检查指定配置的同步任务是否正在运行
    :param config_name: 配置名称，如果为None则检查所有任务
    :return: True表示正在运行，False表示未运行
    """
    try:
        # 检查宝塔任务列表
        t = panelTask.bt_task()
        tasks = public.M('tasks').where("status!=?", ('1',)).field('name,status').select()
        
        if not tasks:
            return False
        
        for task in tasks:
            task_name = task.get('name', '')
            # 检查是否是同步任务
            if '同步' in task_name:
                if config_name:
                    # 检查特定配置的任务
                    if config_name in task_name or task_name.startswith(config_name):
                        helpr.write_log(f"⚠️ 发现正在运行的同步任务: {task_name}")
                        return True
                else:
                    # 任何同步任务都算
                    helpr.write_log(f"⚠️ 发现正在运行的同步任务: {task_name}")
                    return True
        
        return False
    except Exception as e:
        helpr.write_log_error(f"⚠️ 检查任务状态失败: {str(e)}")
        return False


def acquire_lock(config_name):
    """
    获取同步锁（防止同一配置重复执行）
    :param config_name: 配置名称
    :return: 锁文件句柄，如果获取失败返回None
    """
    try:
        lock_file_path = _PLUGIN_PATH + f"config/.sync_{config_name}.lock"
        lock_file = open(lock_file_path, 'w')
        
        # 尝试获取非阻塞锁
        fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        
        # 写入当前进程信息
        lock_file.write(f"PID: {os.getpid()}\nTime: {datetime.now()}\n")
        lock_file.flush()
        
        return lock_file
    except IOError:
        # 锁已被其他进程持有
        return None
    except Exception as e:
        helpr.write_log_error(f"⚠️ 获取锁失败: {str(e)}")
        return None


def release_lock(lock_file):
    """
    释放同步锁
    :param lock_file: 锁文件句柄
    :return: None
    """
    try:
        if lock_file:
            fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
            lock_file.close()
            # 删除锁文件
            try:
                os.remove(lock_file.name)
            except:
                pass
    except Exception as e:
        helpr.write_log_error(f"⚠️ 释放锁失败: {str(e)}")


def check_sync():
    """
    检查并执行需要同步的任务
    :return: None
    """
    try:
        # 获取当前python解释器位置, 防止默认Python版本与面板使用的不一致
        py_bin = psutil.Process(os.getpid()).exe()
        
        # 读取配置文件
        config = helpr.get_config()
        if not config:
            helpr.write_log_error('❌ 未找到配置文件，请先添加同步配置')
            return
        
        if "sync_config_list" not in config:
            helpr.write_log_error('❌ 配置文件中未找到 sync_config_list')
            return
        
        sync_list = config["sync_config_list"]
        if not sync_list or len(sync_list) == 0:
            helpr.write_log('⚠️ 没有配置记录，跳过检查')
            return
        
        now = datetime.now()
        t = panelTask.bt_task()
        
        helpr.write_log('\n' + '='*80, False)
        helpr.write_log(f'🔍 定时调度检查开始 - 当前时间: {now.strftime("%Y-%m-%d %H:%M:%S")}', False)
        helpr.write_log(f'📋 配置数量: {len(sync_list)}', False)
        helpr.write_log('='*80 + '\n', False)
        
        updated = False
        for config_item in sync_list:
            try:
                # 验证配置项必要字段
                if not all(k in config_item for k in ['config_name', 'title', 'hour']):
                    helpr.write_log_error(f"❌ 配置项缺少必要字段: {config_item}")
                    continue
                
                # 构建cron表达式
                hour = int(config_item['hour'])
                time_type = config_item.get('time_type', 'hour')
                
                if time_type == 'minute':
                    cron_expression = f"*/{hour} * * * *"
                    interval_text = f"{hour}分钟"
                else:
                    cron_expression = f"0 */{hour} * * *"
                    interval_text = f"{hour}小时"
                
                # 获取下次同步时间
                # 优先使用配置中保存的 next_sync_time
                saved_next_sync = config_item.get('next_sync_time', 0)
                
                if saved_next_sync > 0:
                    # 使用保存的下次同步时间
                    next_time = datetime.fromtimestamp(saved_next_sync)
                else:
                    # 如果没有保存的时间，重新计算
                    last_sync = config_item.get('last_sync_time', 0)
                    if last_sync > 0:
                        base = datetime.fromtimestamp(last_sync)
                    else:
                        # 首次执行，从当前时间计算
                        base = now
                    
                    cron_iter = croniter(cron_expression, base)
                    next_time = cron_iter.get_next(datetime)
                    # 保存计算的时间
                    config_item['next_sync_time'] = int(next_time.timestamp())
                    updated = True
                
                helpr.write_log(f"\n📌 [{config_item['title']}]")
                helpr.write_log(f"   ⏰ 同步周期: 每{interval_text}一次")
                helpr.write_log(f"   🕐 下次同步: {next_time.strftime('%Y-%m-%d %H:%M:%S')}")
                
                # 判断是否需要执行
                if now >= next_time:
                    # 检查该配置是否有正在运行的任务
                    if is_sync_running(config_item['config_name']):
                        helpr.write_log(f"   ⏸️  跳过执行：上一个同步任务仍在运行中")
                        helpr.write_log(f"   💡 建议：增加同步周期时间，避免任务重叠")
                        continue
                    
                    helpr.write_log(f"   ▶️  开始执行同步任务...")
                    
                    # 从当前 next_time 计算下下次同步时间
                    cron_iter = croniter(cron_expression, next_time)
                    new_next_time = cron_iter.get_next(datetime)
                    
                    # 立即更新最近同步时间（在创建任务前）
                    config_item['last_sync_time'] = int(now.timestamp())
                    config_item['next_sync_time'] = int(new_next_time.timestamp())
                    updated = True
                    
                    # 创建同步任务
                    command = f"{py_bin} {_PLUGIN_PATH}schedule.py run {config_item['config_name']}"
                    result = t.create_task(config_item['title'] + ' 同步', 0, command)
                    
                    if result is not None:
                        helpr.write_log(f"   ✅ 同步任务创建成功，任务ID: {result}")
                        helpr.write_log(f"   📅 最近同步时间已更新: {now.strftime('%Y-%m-%d %H:%M:%S')}")
                        helpr.write_log(f"   ⏰ 下次同步时间: {new_next_time.strftime('%Y-%m-%d %H:%M:%S')}")
                    else:
                        helpr.write_log_error(f"   ❌ 同步任务创建失败")
                        # 任务创建失败，恢复同步时间
                        config_item['last_sync_time'] = last_sync
                        config_item['next_sync_time'] = saved_next_sync
                        updated = False
                else:
                    time_diff = (next_time - now).total_seconds()
                    minutes_left = int(time_diff / 60)
                    helpr.write_log(f"   ⏳ 未到执行时间，还需等待 {minutes_left} 分钟")
                    
            except Exception as e:
                helpr.write_log_error(f"❌ 处理配置项 [{config_item.get('title', 'Unknown')}] 时出错: {str(e)}")
                helpr.write_log_error(f"   错误详情: {public.get_error_info()}")
                continue
        
        # 如果有更新，保存配置
        if updated:
            try:
                helpr.set_config('sync_config_list', sync_list)
                helpr.write_log('💾 配置已更新并保存')
            except Exception as e:
                helpr.write_log_error(f'❌ 保存配置失败: {str(e)}')
        
        helpr.write_log('\n' + '='*80, False)
        helpr.write_log('✅ 定时调度检查完成', False)
        helpr.write_log('='*80 + '\n', False)
        
    except Exception as e:
        helpr.write_log_error(f'❌ 定时调度检查失败: {str(e)}')
        helpr.write_log_error(f'错误详情: {public.get_error_info()}')

def run(sync_config):
    """
    执行同步任务
    :param sync_config: 同步配置字典
    :return: None
    """
    lock_file = None
    try:
        if not sync_config:
            helpr.write_log_error('❌ 同步配置为空')
            return
        
        title = sync_config.get('title', 'Unknown')
        config_name = sync_config.get('config_name', 'unknown')
        
        # 尝试获取锁
        lock_file = acquire_lock(config_name)
        if not lock_file:
            helpr.write_log(f'⏸️  同步任务 [{title}] 已在运行中，跳过本次执行')
            helpr.write_log(f'💡 建议：增加同步周期时间，当前任务尚未完成')
            return
        
        helpr.write_log('\n' + '='*80, False)
        helpr.write_log(f'🚀 开始执行同步任务: [{title}]', False)
        helpr.write_log(f'⏰ 开始时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', False)
        helpr.write_log(f'🔒 已获取执行锁，防止重复运行', False)
        helpr.write_log('='*80 + '\n', False)
        
        start_time = datetime.now()
        
        # 执行同步
        sync = psync.psync(sync_config)
        sync.run()
        
        end_time = datetime.now()
        elapsed = (end_time - start_time).total_seconds()
        minutes = int(elapsed // 60)
        seconds = int(elapsed % 60)
        
        helpr.write_log('\n' + '='*80, False)
        helpr.write_log(f'✅ 同步任务完成: [{title}]', False)
        helpr.write_log(f'⏰ 完成时间: {end_time.strftime("%Y-%m-%d %H:%M:%S")}', False)
        helpr.write_log(f'⏱️  总耗时: {minutes}分{seconds}秒', False)
        helpr.write_log('='*80 + '\n', False)
        
    except Exception as e:
        helpr.write_log_error(f'❌ 同步任务执行失败: {str(e)}')
        helpr.write_log_error(f'错误详情: {public.get_error_info()}')
        raise
    finally:
        # 确保释放锁
        if lock_file:
            release_lock(lock_file)
            helpr.write_log('🔓 已释放执行锁')


if __name__ == '__main__':
    try:
        if len(sys.argv) <= 1:
            helpr.write_log_error('❌ 缺少参数，请指定操作类型 (check_sync 或 run)')
            sys.exit(1)
        
        action = sys.argv[1]
        
        # 检查同步任务
        if action == 'check_sync':
            check_sync()
            
        # 运行同步任务
        elif action == 'run':
            if len(sys.argv) > 2:
                # 运行指定配置
                config_name = sys.argv[2]
                helpr.write_log(f'📝 运行指定配置: {config_name}')
                
                config = helpr.get_config()
                if not config:
                    helpr.write_log_error('❌ 未找到配置文件')
                    sys.exit(1)
                
                if "sync_config_list" not in config:
                    helpr.write_log_error('❌ 配置文件中未找到 sync_config_list')
                    sys.exit(1)
                
                # 查找指定配置
                found = False
                for config_item in config["sync_config_list"]:
                    if config_item.get('config_name') == config_name:
                        run(config_item)
                        found = True
                        break
                
                if not found:
                    helpr.write_log_error(f'❌ 未找到配置: {config_name}')
                    sys.exit(1)
            else:
                # 运行所有配置
                helpr.write_log('📝 运行所有同步配置')
                
                config = helpr.get_config()
                if not config:
                    helpr.write_log_error('❌ 未找到配置文件')
                    sys.exit(1)
                
                if "sync_config_list" not in config:
                    helpr.write_log_error('❌ 配置文件中未找到 sync_config_list')
                    sys.exit(1)
                
                sync_list = config["sync_config_list"]
                if not sync_list or len(sync_list) == 0:
                    helpr.write_log_error('⚠️ 没有配置记录')
                    sys.exit(0)
                
                t = panelTask.bt_task()
                py_bin = psutil.Process(os.getpid()).exe()
                
                success_count = 0
                fail_count = 0
                
                for config_item in sync_list:
                    try:
                        title = config_item.get('title', 'Unknown')
                        config_name = config_item.get('config_name', '')
                        
                        if not config_name:
                            helpr.write_log_error(f'⚠️ 配置项缺少 config_name: {config_item}')
                            fail_count += 1
                            continue
                        
                        command = f"{py_bin} {_PLUGIN_PATH}schedule.py run {config_name}"
                        result = t.create_task(title + ' 同步', 0, command)
                        
                        if result is not None:
                            helpr.write_log(f"✅ [{title}] 同步任务创建成功，任务ID: {result}")
                            success_count += 1
                        else:
                            helpr.write_log_error(f"❌ [{title}] 同步任务创建失败")
                            fail_count += 1
                    except Exception as e:
                        helpr.write_log_error(f"❌ 创建任务失败: {str(e)}")
                        fail_count += 1
                        continue
                
                helpr.write_log(f'\n📊 批量运行结果: 成功 {success_count} 个，失败 {fail_count} 个\n')
        else:
            helpr.write_log_error(f'❌ 未知操作类型: {action}')
            helpr.write_log('💡 支持的操作: check_sync, run')
            sys.exit(1)
            
    except KeyboardInterrupt:
        helpr.write_log('\n⚠️ 用户中断执行')
        sys.exit(130)
    except Exception as e:
        helpr.write_log_error(f'❌ 程序执行失败: {str(e)}')
        helpr.write_log_error(f'错误详情: {public.get_error_info()}')
        sys.exit(1)